package com.hu.log.aspect;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.hu.common.annotation.Log;
import com.hu.common.common.Constants;
import com.hu.common.pojo.Blog;
import com.hu.common.pojo.SysLog;
import com.hu.common.pojo.User;
import com.hu.common.pojo.dto.Response;
import com.hu.log.service.ISysLogService;
import com.hu.log.utils.TokenUtils;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * 系统日志切面
 *
 * @author CL
 *
 */
@Log4j2
@Aspect
@Component
public class SysLogAspect {

    private ThreadLocal<SysLog> sysLogThreadLocal = new ThreadLocal<>();

    @Autowired
    private Executor customThreadPoolTaskExecutor;

    @Autowired
    private ISysLogService sysLogService;

    /**
     * 定义切入点：在哪些类和哪些方法切入
     */
    @Pointcut("execution(public * com.my.blogback.controller.BlogController.save*(..)) && !execution(* com.my.blogback.controller.*.find*(..))")
    public void sysLogAspect() {
    }


    /**
     * 前置通知：在目标方法执行前调用
     */
//    @Before("sysLogAspect()")
//    public void begin() {
//        System.out.println("==@Before== lingyejun blog logger : begin");
//    }


    /**
     * 前置通知
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Before(value = "sysLogAspect()")
    public void doBefore(JoinPoint  joinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) Objects
                .requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        User currentUser = TokenUtils.getCurrentUser();
        SysLog sysLog = new SysLog();
        // 创建人信息请根据实际项目获取方式获取
        if (ObjectUtil.isNotEmpty(currentUser)) {
            sysLog.setCreateUserCode(String.valueOf(currentUser.getId()));
            sysLog.setCreateUserName(currentUser.getNickname());
            sysLog.setAuthorAvatar(currentUser.getAvatarUrl());
        }

        // 获取注解参数值
        // 1. toString后截取字符串获取
        // joinPoint.getArgs()[0].toString()
        // 2. 强转为blog类型，使用getXxx方法获取
        Blog blog = (Blog) joinPoint.getArgs()[0];

        // 获取Log注解的successInfo
        Log annotationInfo = getDeclaredAnnotation(joinPoint);
        sysLog.setCreateDate(DateUtil.now());
        sysLog.setRequestUrl(URLUtil.getPath(request.getRequestURI()));
        sysLog.setRequestParams(formatParams(request.getParameterMap()));
        sysLog.setRequestMethod(request.getMethod());
        sysLog.setRequestIp(ServletUtil.getClientIP(request));
        sysLog.setServerAddress(request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort());
        String userAgentStr = request.getHeader("User-Agent");
        sysLog.setUserAgent(userAgentStr);
        UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
        sysLog.setDeviceName(userAgent.getOs().getName());
        sysLog.setBrowserName(userAgent.getBrowser().getName());

        // 组织日志的完整successInfo
        String s = currentUser.getNickname()+ "发布了博客: " + blog.getTitle();

        sysLog.setSuccessInfo(s);
        sysLogThreadLocal.set(sysLog);
    }

    /**
     * 返回通知
     *
     * @param ret
     */
    @AfterReturning(pointcut = "sysLogAspect()", returning = "ret")
    public void doAfterReturning(Object ret) {
        SysLog sysLog = sysLogThreadLocal.get();
        sysLog.setLogType(Constants.LOG_INGO);
        Response<?> r = Convert.convert(Response.class, ret);
        if (r.getCode() == 200) {
            sysLog.setIsException(Constants.NO);
        } else {
            sysLog.setIsException(Constants.YES);
            sysLog.setExceptionInfo(r.getMsg());
        }
        customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
        sysLogThreadLocal.remove();

        Runtime runtime = Runtime.getRuntime();
    }

    /**
     * 异常通知
     *
     * @param e
     */
    @AfterThrowing(pointcut = "sysLogAspect()", throwing = "e")
    public void doAfterThrowable(Throwable e) {
        SysLog sysLog = sysLogThreadLocal.get();
        sysLog.setLogType(Constants.LOG_ERROR);
        sysLog.setIsException(Constants.YES);
        sysLog.setExceptionInfo(e.getMessage());
        customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
        sysLogThreadLocal.remove();

        Runtime runtime = Runtime.getRuntime();
    }

    /**
     * 格式化参数
     *
     * @param parameterMap
     * @return
     */
    private String formatParams(Map<String, String[]> parameterMap) {
        if (parameterMap == null) {
            return null;
        }
        StringBuilder params = new StringBuilder();
        for (Map.Entry<String, String[]> param : (parameterMap).entrySet()) {
            if (params.length() != 0) {
                params.append("&");
            }
            params.append(param.getKey() + "=");
            if (StrUtil.endWithIgnoreCase(param.getKey(), "password")) {
                params.append("*");
            } else if (param.getValue() != null) {
                params.append(ArrayUtil.join(param.getValue(), ","));
            }
        }
        return params.toString();
    }

    /**
     * 保存日志线程
     *
     * @author CL
     *
     */
    private static class SaveLogThread extends Thread {
        private SysLog sysLog;
        private ISysLogService sysLogService;

        public SaveLogThread(SysLog sysLog, ISysLogService sysLogService) {
            this.sysLog = sysLog;
            this.sysLogService = sysLogService;
        }

        @Override
        public void run() {
            sysLogService.save(sysLog);
        }
    }

    /**
     * 获取方法中声明的注解
     *
     * @param joinPoint
     * @return
     * @throws NoSuchMethodException
     */
    public Log getDeclaredAnnotation(JoinPoint joinPoint) throws NoSuchMethodException {
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();

        // 反射获取目标类
        Class<?> targetClass = joinPoint.getTarget().getClass();
        // 拿到方法对应的参数类型
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        // 根据类、方法、参数类型（重载）获取到方法的具体信息
        Method objMethod = targetClass.getMethod(methodName, parameterTypes);

        // 拿到方法定义的注解信息
        Log annotation = objMethod.getDeclaredAnnotation(Log.class);
        // 返回
        return annotation;
    }
}